﻿using Fast.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Polly;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Fast.RemoteRequest
{
    public class HttpRemoteRequest : IHttpRemoteRequest
    {
        private readonly IHttpClientFactory _httpClientFactory;

        /// <summary>
        /// 客户端名称
        /// </summary>
        protected string ClientName { get; set; }

        /// <summary>
        /// 请求 Url
        /// </summary>
        protected string RequestUrl { get; set; }

        /// <summary>
        /// 超时时间（秒）
        /// </summary>
        protected TimeSpan? Timeout { get; set; }

        /// <summary>
        /// 重试次数
        /// </summary>
        public int RetryCount { get; set; }

        /// <summary>
        /// 设置查询参数
        /// </summary>
        protected IDictionary<string, object> QueryParams { get; set; }

        /// <summary>
        /// 请求报文 Headers
        /// </summary>
        protected IDictionary<string, object> Headers { get; set; }

        /// <summary>
        /// 请求报文 Body 参数
        /// </summary>
        protected object Body { get; set; }

        /// <summary>
        /// 请求报文 Body 内容类型
        /// </summary>
        protected string ContentType { get; set; } = "application/json";

        /// <summary>
        /// 内容编码
        /// </summary>
        protected Encoding ContentEncoding { get; set; } = Encoding.UTF8;

        /// <summary>
        /// 设置 Body Bytes 类型
        /// </summary>
        protected List<(string Name, byte[] Bytes, string FileName)> BodyBytes { get; set; }

        protected HttpRemoteRequestOptions Options { get; set; }

        protected ILogger<HttpRemoteRequest> Logger { get; set; }

        public HttpRemoteRequest(
            IHttpClientFactory httpClientFactory,
            ILogger<HttpRemoteRequest> logger,
            IOptions<HttpRemoteRequestOptions> options)
        {
            Check.NotNull(httpClientFactory, nameof(httpClientFactory), "请调用services.AddRemoteRequest方法提供服务支持");
            Check.NotNull(logger, nameof(ILogger<HttpRemoteRequest>));
            Check.NotNull(options.Value, nameof(HttpRemoteRequestOptions));

            _httpClientFactory = httpClientFactory;
            Options = options.Value;
            Logger = logger;

            Headers = new Dictionary<string, object>();
            BodyBytes = new List<(string Name, byte[] Bytes, string FileName)>();
        }

        /// <summary>
        /// 设置请求Url
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        public virtual IHttpRemoteRequest SetRequestUrl(string url)
        {
            if (string.IsNullOrWhiteSpace(url))
                return this;

            RequestUrl = url.StartsWith("/") ? url[1..] : url;

            return this;
        }

        /// <summary>
        /// 设置客户端分类名
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public virtual IHttpRemoteRequest SetClientName(string name)
        {
            if (!name.IsNullOrWhiteSpace())
                ClientName = name;

            return this;
        }

        /// <summary>
        /// 设置超时时间（秒）
        /// </summary>
        /// <param name="timeout"></param>
        /// <returns></returns>
        public virtual IHttpRemoteRequest SetClientTimeout(TimeSpan timeout)
        {
            if (timeout.TotalSeconds > 0) Timeout = timeout;
            return this;
        }

        /// <summary>
        /// 设置超时时间（秒）
        /// </summary>
        /// <param name="timeout"></param>
        /// <returns></returns>
        public virtual IHttpRemoteRequest SetClientTimeout(long timeout)
        {
            if (timeout > 0) Timeout = TimeSpan.FromSeconds(timeout);
            return this;
        }

        /// <summary>
        /// 设置重试次数
        /// </summary>
        /// <param name="retryCount"></param>
        /// <returns></returns>
        public virtual IHttpRemoteRequest SetRetryCount(int retryCount)
        {
            if (retryCount > 0) RetryCount = retryCount;
            return this;
        }

        /// <summary>
        /// 设置 URL 参数
        /// </summary>
        /// <param name="queryParams"></param>
        /// <returns></returns>
        public virtual IHttpRemoteRequest SetQueryParams(IDictionary<string, object> queryParams)
        {
            if (queryParams != null)
                QueryParams = queryParams;

            return this;
        }

        /// <summary>
        /// 设置 URL 参数
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public virtual IHttpRemoteRequest SetQueryParams(string key, object value)
        {
            if (QueryParams == null)
                QueryParams = new Dictionary<string, object>();

            QueryParams.Add(key, value);

            return this;
        }
        /// <summary>
        /// 设置 Authorization
        /// </summary>
        /// <param name="type"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public virtual IHttpRemoteRequest SetAuthorization(AuthorizationType type, object value)
        {
            Headers.Add(type.GetDescription(), value);
            return this;
        }

        /// <summary>
        /// 设置请求头
        /// </summary>
        /// <param name="headers"></param>
        /// <returns></returns>
        public virtual IHttpRemoteRequest SetHeaders(IDictionary<string, object> headers)
        {
            if (headers != null && headers.Count > 0)
            {
                foreach (var item in headers)
                {
                    Headers.Add(item);
                }
            }

            return this;
        }

        /// <summary>
        /// 设置请求头
        /// </summary>
        /// <param name="headers"></param>
        /// <returns></returns>
        public virtual IHttpRemoteRequest SetHeaders(string key, object value)
        {
            Headers.Add(key, value);

            return this;
        }

        /// <summary>
        /// 设置请求头
        /// </summary>
        /// <param name="headers"></param>
        /// <returns></returns>
        public virtual IHttpRemoteRequest SetBody(object body, string contentType = default, Encoding encoding = default)
        {
            if (body != null) Body = body;
            SetContentType(contentType);
            SetContentEncoding(encoding);

            return this;
        }

        /// <summary>
        /// 设置 Body  Bytes
        /// </summary>
        /// <param name="bytesData"></param>
        /// <returns></returns>
        public virtual IHttpRemoteRequest SetBodyBytes(params (string Name, byte[] Bytes, string FileName)[] bytesData)
        {
            if (bytesData != null && bytesData.Length > 0) BodyBytes.AddRange(bytesData);

            return this;
        }

        /// <summary>
        /// 设置内容类型
        /// </summary>
        /// <param name="contentType"></param>
        /// <returns></returns>
        public virtual IHttpRemoteRequest SetContentType(string contentType)
        {
            if (!contentType.IsNullOrWhiteSpace())
                ContentType = contentType;

            return this;
        }

        /// <summary>
        /// 设置内容编码
        /// </summary>
        /// <param name="encoding"></param>
        /// <returns></returns>
        public virtual IHttpRemoteRequest SetContentEncoding(Encoding encoding)
        {
            if (encoding != null)
                ContentEncoding = encoding;

            return this;
        }

        /// <summary>
        /// 发送请求返回 HttpResponseMessage 对象
        /// </summary>
        /// <param name="httpMethod"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task<HttpResponseMessage> SendAsync(HttpMethod httpMethod,
            CancellationToken cancellationToken = default)
        {
            Check.NotNull(httpMethod, nameof(httpMethod));
            if (string.IsNullOrWhiteSpace(RequestUrl))
                throw new NullReferenceException(RequestUrl);

            //构建请求对象
            var httpRequest = new HttpRequestMessage(httpMethod, RequestUrl);

            //解析查询参数
            ResolveQueryParams(httpRequest);

            // 设置请求报文头
            foreach (var header in Headers)
            {
                if (header.Value != null)
                    httpRequest.Headers.Add(header.Key, header.Value.ToString());
            }

            //设置消息内容
            SetHttpContent(httpMethod, httpRequest);

            //创建 HttpClient 对象前拦截
            Options.BeforeCreateHttpClient?.Invoke(httpRequest);

            // 创建 HttpClient 对象
            using var httpClient = ClientName.IsNullOrWhiteSpace() ? _httpClientFactory.CreateClient() : _httpClientFactory.CreateClient(ClientName);

            Options.ConfigureClient?.Invoke(httpClient);

            if (httpClient.BaseAddress != null &&
                httpClient.BaseAddress.OriginalString.IsNotNullOrWhiteSpace() &&
                !httpClient.BaseAddress.OriginalString.EndsWith('/'))
            {
                httpClient.BaseAddress = new Uri($"{httpClient.BaseAddress.OriginalString}/");
            }

            // 设置超时时间
            if (Timeout.HasValue && Timeout.Value.TotalSeconds > 0)
            {
                // 设置请求超时时间
                httpClient.Timeout = Timeout.Value;
            }

            // 添加默认 User-Agent
            if (!httpClient.DefaultRequestHeaders.Contains("User-Agent"))
            {
                httpClient.DefaultRequestHeaders.Add("User-Agent",
                    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Safari/537.36 Edg/91.0.864.48");
            }

            // 在发送 Http 请求前拦截
            Options.BeforeSendHttpRequest?.Invoke(httpRequest);

            var url = $"{httpClient.BaseAddress?.OriginalString}{httpRequest.RequestUri?.OriginalString}";
            // 打印发送请求
            App.PrintToMiniProfiler(MiniProfilerCategory.HttpClient.GetDescription(), "Sending",
                $"[{httpMethod}] {url}");

            Exception exception = default;
            HttpResponseMessage responseMessage = default;
            try
            {
                httpRequest.SetPolicyExecutionContext(new Context(url));
                responseMessage = await httpClient.SendAsync(httpRequest, cancellationToken);
            }
            catch (Exception ex)
            {
                exception = ex;
            }

            if (responseMessage?.IsSuccessStatusCode == true && exception == default)
            {
                // 打印成功请求
                App.PrintToMiniProfiler(MiniProfilerCategory.HttpClient.GetDescription(), "Succeeded",
                    $"[StatusCode: {responseMessage.StatusCode}] Succeeded");

                Options.BeforeHttpResponse?.Invoke(responseMessage);
            }
            else
            {
                // 读取错误消息
                var errors = exception == null && responseMessage != null
                    ? await responseMessage.Content.ReadAsStringAsync(cancellationToken)
                    : exception?.Message;

                // 打印失败请求
                App.PrintToMiniProfiler(MiniProfilerCategory.HttpClient.GetDescription(), "Failed",
                    $"[StatusCode: {responseMessage?.StatusCode}] {errors}", true);

                //异常拦截器
                Options.OnException?.Invoke(responseMessage, errors);
            }

            return responseMessage;
        }

        /// <summary>
        /// 解析查询参数
        /// </summary>
        /// <param name="httpRequest"></param>
        /// <param name="isEncode">是否需要对查询参数进行转义</param>
        protected virtual void ResolveQueryParams(HttpRequestMessage httpRequest, bool isEncode = true)
        {
            if (QueryParams == null || QueryParams.Count == 0) return;

            var originalRequestUrl = httpRequest.RequestUri.OriginalString;

            var queryParams = QueryParams.Where(u => u.Value != null)
                .Select(u => $"{u.Key}={(isEncode ? Uri.EscapeDataString(u.Value.ToString()) : u.Value.ToString())}");
            originalRequestUrl +=
                $"{(originalRequestUrl.IndexOf("?") > -1 ? "&" : "?")}{string.Join("&", queryParams)}";

            // 重新设置地址
            httpRequest.RequestUri = new Uri(originalRequestUrl, UriKind.RelativeOrAbsolute);
        }

        protected virtual void SetHttpContent(HttpMethod httpMethod, HttpRequestMessage request)
        {
            // GET/HEAD 请求不支持设置 Body 请求
            if (httpMethod == HttpMethod.Get || httpMethod == HttpMethod.Head) return;

            HttpContent httpContent = null;

            // 处理各种 Body 类型
            switch (ContentType)
            {
                case "multipart/form-data":
                    var multipartFormDataContent = new MultipartFormDataContent();

                    // 添加 Bytes 类型
                    foreach (var (Name, Bytes, FileName) in BodyBytes)
                    {
                        if (string.IsNullOrWhiteSpace(FileName))
                            multipartFormDataContent.Add(new ByteArrayContent(Bytes), Name);
                        else
                            multipartFormDataContent.Add(new ByteArrayContent(Bytes), Name, FileName);
                    }

                    // 处理其他类型
                    var dic = ConvertBodyToDictionary();
                    if (dic != null && dic.Count > 0)
                    {
                        foreach (var (key, value) in dic)
                        {
                            multipartFormDataContent.Add(new StringContent(value, ContentEncoding),
                                string.Format("\"{0}\"", key));
                        }
                    }

                    // 设置内容类型
                    httpContent = multipartFormDataContent;
                    break;

                case "application/octet-stream":
                    if (BodyBytes.Count > 0 && BodyBytes[0].Bytes.Length > 0)
                    {
                        httpContent = new ByteArrayContent(BodyBytes[0].Bytes);

                        // 设置内容类型
                        httpContent.Headers.ContentType = new MediaTypeHeaderValue(ContentType);
                    }

                    break;

                case "application/json":
                case "text/json":
                case "application/*+json":
                    if (Body != null)
                    {
                        httpContent = new StringContent(App.JsonSerializer.Serialize(Body), ContentEncoding);

                        // 设置内容类型
                        httpContent.Headers.ContentType = new MediaTypeHeaderValue(ContentType);
                    }

                    break;

                case "application/x-www-form-urlencoded":
                    // 解析字典
                    var keyValues = ConvertBodyToDictionary();

                    if (keyValues == null || keyValues.Count == 0) return;

                    // 设置内容类型
                    httpContent = new FormUrlEncodedContent(keyValues);
                    break;

                case "application/xml":
                case "text/xml":
                    if (Body != null) httpContent = new StringContent(Body.ToString(), ContentEncoding, ContentType);
                    break;

                default:
                    // 其他类型可通过 `HttpRequestMessage` 拦截器设置
                    break;
            }

            // 设置 HttpContent
            if (httpContent != null) request.Content = httpContent;
        }

        /// <summary>
        /// 转换 Body 为 字典类型
        /// </summary>
        /// <returns></returns>
        private IDictionary<string, string> ConvertBodyToDictionary()
        {
            IDictionary<string, string> keyValues = null;
            if (Body == null) return default;

            // 处理各种情况
            if (Body is IDictionary<string, string> dic) keyValues = dic;
            else if (Body is IDictionary<string, object> dicObj)
                keyValues = dicObj.ToDictionary(u => u.Key, u => App.JsonSerializer.Serialize(u.Value));
            else keyValues = Body.ToDictionary().ToDictionary(u => u.Key, u => App.JsonSerializer.Serialize(u.Value));

            return keyValues;
        }
    }
}